package tcss543.cpap.rinkesh;

import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import tcss543.cpap.common.Constants;
import tcss543.cpap.common.ReviewerRank;
import tcss543.cpap.data.DataGenerator;
import tcss543.cpap.data.DataSetSkewType;
import tcss543.cpap.data.PaperSetSize;
import drasys.or.matrix.VectorI;
import drasys.or.mp.Constraint;
import drasys.or.mp.ConstraintI;
import drasys.or.mp.ConvergenceException;
import drasys.or.mp.DuplicateException;
import drasys.or.mp.InfeasibleException;
import drasys.or.mp.InvalidException;
import drasys.or.mp.NoSolutionException;
import drasys.or.mp.NotFoundException;
import drasys.or.mp.Problem;
import drasys.or.mp.ScaleException;
import drasys.or.mp.SizableProblemI;
import drasys.or.mp.UnboundedException;
import drasys.or.mp.Variable;
import drasys.or.mp.lp.DenseSimplex;
import drasys.or.mp.lp.LinearProgrammingI;

/**
 * This class provides methods to solve the CPAP problem using linear
 * programming approach.
 * 
 * @author Rineksh Nagmoti
 * 
 */
public class LinearProgramming {

	FileWriter fw;

	/**
	 * Constructor for the class.
	 */
	public LinearProgramming() {
	}

	/**
	 * This method enables solving CPAP by Linear Programming Approach. We need
	 * to specify the size of PapersSet using Enumerated variable PaperSetSize
	 * and then class method conduct experiments.
	 * 
	 * @param args
	 */
	public static void main(String args[]) {
		LinearProgramming lpp = new LinearProgramming();

		try {
			lpp.conductExperiments(PaperSetSize.VERY_SMALL);
			Runtime.getRuntime().gc();
			lpp.conductExperiments(PaperSetSize.SMALL);
			Runtime.getRuntime().gc();
			lpp.conductExperiments(PaperSetSize.MEDIUM);
			Runtime.getRuntime().gc();
			lpp.conductExperiments(PaperSetSize.STANDARD);
			Runtime.getRuntime().gc();
			
			lpp.conductExperiments(PaperSetSize.LARGE);
			Runtime.getRuntime().gc();
			lpp.conductExperiments(PaperSetSize.VERY_LARGE);
			Runtime.getRuntime().gc();
			lpp.conductExperiments(PaperSetSize.HUMUNGOUS);
			Runtime.getRuntime().gc();
			lpp.conductExperiments(PaperSetSize.MAX);
			Runtime.getRuntime().gc();

		} catch (NoSolutionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (DuplicateException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (UnboundedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InfeasibleException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ConvergenceException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ScaleException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvalidException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	/**
	 * This method conducts 4 experiments for each dataset provided.
	 * 
	 * @param p
	 * @throws NoSolutionException
	 * @throws DuplicateException
	 * @throws NotFoundException
	 * @throws UnboundedException
	 * @throws InfeasibleException
	 * @throws ConvergenceException
	 * @throws ScaleException
	 * @throws InvalidException
	 * @throws IOException
	 */
	public void conductExperiments(PaperSetSize p) throws NoSolutionException,
			DuplicateException, NotFoundException, UnboundedException,
			InfeasibleException, ConvergenceException, ScaleException,
			InvalidException, IOException {
		String file = "results_" + p.name() + ".txt";
		fw = new FileWriter(file);

		DataGenerator dg = new DataGenerator(p, DataSetSkewType.RANDOM);
		System.out.println("Dataset " + p.name());
		fw.write("Dataset " + p.name() + "\n");
		Map<Short, Map<Short, ReviewerRank>> reviewers = dg.getReviewers();
		Map<Short, Set<Short>> papers = dg.getPapers();
		short experimentNo = 1;
		long firstTestTime = 0, secondTestTime = 0, thirdTestTime = 0, fourthTestTime = 0;
		double quality1 = 0, quality2 = 0, quality3 = 0, quality4 = 0;
		while (experimentNo <= 4) 
		{
			long start = System.currentTimeMillis();
			Map<Short, Set<Short>> solution = getSolution(reviewers, papers);
			long end = System.currentTimeMillis();

			switch (experimentNo) {
			case 1:
				quality1 = tcss543.cpap.common.Utilities.calculateQuality(
						solution, papers, reviewers, true).doubleValue();
				System.out.println("Quality of Solution" + experimentNo + " = "
						+ quality1);
				fw.write("Quality of Solution" + experimentNo + " = "
						+ quality1 + "\n");
				firstTestTime = end - start;
				break;
			case 2:
				quality2 = tcss543.cpap.common.Utilities.calculateQuality(
						solution, papers, reviewers, true).doubleValue();
				secondTestTime = end - start;
				System.out.println("Quality of Solution" + experimentNo + " = "
						+ quality2);
				fw.write("Quality of Solution" + experimentNo + " = "
						+ quality2 + "\n");
				break;
			case 3:
				quality3 = tcss543.cpap.common.Utilities.calculateQuality(
						solution, papers, reviewers, true).doubleValue();
				thirdTestTime = end - start;
				System.out.println("Quality of Solution" + experimentNo + " = "
						+ quality3);
				fw.write("Quality of Solution" + experimentNo + " = "
						+ quality3 + "\n");
				break;
			case 4:
				quality4 = tcss543.cpap.common.Utilities.calculateQuality(
						solution, papers, reviewers, true).doubleValue();
				fourthTestTime = end - start;
				System.out.println("Quality of Solution" + experimentNo + " = "
						+ quality4 + "\n");
				fw.write("Quality of Solution" + experimentNo + " = "
						+ quality4 + "\n");
				break;
			}

			experimentNo++;
			Runtime.getRuntime().gc();
		}
		fw.write("Avg. qualtiy of solution = "
				+ (quality1 + quality2 + quality3 + quality4) / 4 + "\n");
		System.out.println("Time Taken by experiment 1 = " + firstTestTime);
		fw.write("Time Taken by experiment 1 = " + firstTestTime + "\n");
		System.out.println("Time Taken by experiment 2 = " + secondTestTime);
		fw.write("Time Taken by experiment 2 = " + secondTestTime + "\n");
		System.out.println("Time Taken by experiment 3 = " + thirdTestTime);
		fw.write("Time Taken by experiment 3 = " + thirdTestTime + "\n");
		System.out.println("Time Taken by experiment 4 = " + fourthTestTime);
		fw.write("Time Taken by experiment 4 = " + fourthTestTime + "\n");
		System.out
				.println("Avg. Time for Experiments = "
						+ (firstTestTime + secondTestTime + thirdTestTime + fourthTestTime)
						/ 4);
		fw
				.write("Avg.Time taken for Experiments "
						+ (firstTestTime + secondTestTime + thirdTestTime + fourthTestTime)
						/ 4 + "\n");
		fw.flush();
	}

	/**
	 * This method calls the OpResaerch jar file to solve the problem using
	 * Dense coefficient simplex method.
	 * 
	 * @param reviewers
	 * @param papers
	 * @return
	 * @throws NoSolutionException
	 * @throws DuplicateException
	 * @throws NotFoundException
	 * @throws UnboundedException
	 * @throws InfeasibleException
	 * @throws ConvergenceException
	 * @throws ScaleException
	 * @throws InvalidException
	 */

	public Map<Short, Set<Short>> getSolution
	(
			Map<Short, Map<Short, ReviewerRank>> reviewers,
			Map<Short, Set<Short>> papers) throws NoSolutionException,
			DuplicateException, NotFoundException, UnboundedException,
			InfeasibleException, ConvergenceException, ScaleException,
			InvalidException {
		ArrayList<String> assignments = new ArrayList<String>();
		ArrayList<Paper> paps = new ArrayList<Paper>();
		ArrayList<Reviewer> revs = new ArrayList<Reviewer>();
		Map<Short, Set<Short>> solution = new HashMap<Short, Set<Short>>();
		SizableProblemI prob = new Problem(reviewers.keySet().size()
				* (papers.keySet().size() + 1), 2 * reviewers.keySet().size()
				* papers.keySet().size() + 2 * reviewers.keySet().size()
				+ papers.keySet().size() + 1);
		prob.getMetadata().put("lp.isMaximize", "true");
		final byte LTE = Constraint.LESS;
		final byte GTE = Constraint.GREATER;
		final byte rTE = Constraint.RANGE;
		final byte RTE = Variable.INTEGER;
		for (short pid : papers.keySet()) {
			Paper p = new Paper(pid, papers.get(pid));
			paps.add(p);
		}

		for (short id : reviewers.keySet()) {
			Reviewer r = new Reviewer(id, reviewers.get(id));
			revs.add(r);
			for (short i = 0; i < paps.size(); i++) {
				Paper p = paps.get(i);
				short qualitySingle = getSingleAssignmentQuality(r, p);
				String var = "A" + p.paper_id + "and" + id;
				prob.newVariable(var).setObjectiveCoefficient(qualitySingle);
				//prob.newVariable(var).setObjectiveCoefficient(qualitySingle).setLowerBound(0.0).setUpperBound(1.0);
				
				assignments.add(var);
				// System.out.println("For Reviewer " + id +" and for Paper " +
				// p.paper_id + " quality = " + qualitySingle);
			}
		}

		for (int i = 0; i < revs.size(); i++) {
			Reviewer rev = revs.get(i);
			prob.newVariable("h" + rev.reviewer_id).setObjectiveCoefficient(
					-1.0);
		}

		prob.newConstraint("total").setType(LTE).setRightHandSide(
				3 * Constants.MIN_NUM_OF_REVIEWERS_PER_PAPER * papers.size());

		for (int j = 0; j < revs.size(); j++) {
			short id = revs.get(j).reviewer_id;

			String rabsCon1 = "rabsplus" + id;
			String rabsCon2 = "rabsminus" + id;
			prob.newConstraint(rabsCon1).setType(LTE).setRightHandSide(
					Constants.OPTIMAL_PAPERS_PER_REVIEWER);
			prob.newConstraint(rabsCon2).setType(LTE).setRightHandSide(
					-1 * Constants.OPTIMAL_PAPERS_PER_REVIEWER);
			for (short i = 0; i < paps.size(); i++) {
				Paper p = paps.get(i);

				String var = "A" + p.paper_id + "and" + id;

				prob.setCoefficientAt("total", var, 1.0);
				prob.setCoefficientAt(rabsCon1, var, 1.0);
				prob.setCoefficientAt(rabsCon2, var, -1.0);
                
		     	// prob.newConstraint(var + "zeroone").setType(rTE).setUpperRange(1.0).setLowerRange(0.0);
				// prob.newConstraint(var + "zeroone").setType(rTE).setLowerRange(0.0);
			//	constraint.setLowerRange(0.0);
				prob.newConstraint(var + "zero").setType(GTE).setRightHandSide(0.0);
				prob.newConstraint(var + "one").setType(LTE).setRightHandSide(1.0);
		    //	prob.setCoefficientAt(var + "zeroone", var, 1.0);
				prob.setCoefficientAt(var + "zero", var, 1.0);
				prob.setCoefficientAt(var + "one", var, 1.0);

			}

			prob.setCoefficientAt(rabsCon1, "h" + id, -1.0);
			prob.setCoefficientAt(rabsCon2, "h" + id, -1.0);
			// revs.add(r);
		}

		for (short i = 0; i < paps.size(); i++) {
			Paper p = paps.get(i);
			String gammaCon = "gamma" + p.paper_id;
			prob.newConstraint(gammaCon).setType(GTE).setRightHandSide(
					Constants.MIN_NUM_OF_REVIEWERS_PER_PAPER);
			for (short k = 0; k < revs.size(); k++) {
				Reviewer r = revs.get(k);
				String var = "A" + p.paper_id + "and" + r.reviewer_id;
				prob.setCoefficientAt(gammaCon, var, 1.0);
			}
		}

		LinearProgrammingI lp = new DenseSimplex(prob);
		double ans = lp.solve();
		// System.out.println("######"+ans);

		VectorI i = lp.getSolution();
		// Enumeration e = i.elements();

		// e.toString();
		System.out.println("Solution = " + lp.getObjectiveValue());
		short cnt = 0;
	//	Map<Short,Short> paperReviewers = new HashMap<Short, Short>();
		
		for (short cnt1 = 0; cnt1 < revs.size(); cnt1++) 
		{
			Set<Short> list = new HashSet<Short>();
			for (short cnt2 = 0; cnt2 < paps.size(); cnt2++)
			{

				if (i.elementAt(cnt) != 0.0) 
				{
					// System.out.println(i.elementAt(cnt));
					list.add(paps.get(cnt2).paper_id);
					
//					if(paperReviewers.containsKey(paps.get(cnt2).paper_id))
//					{
//						short count = paperReviewers.get(paps.get(cnt2).paper_id);
//					    count++;
//					    paperReviewers.remove(paps.get(cnt2).paper_id);
//					    paperReviewers.put(paps.get(cnt2).paper_id, count);
//					}
//					else
//					{
//						paperReviewers.put(paps.get(cnt2).paper_id, (short)1);	
//					}
				}
				cnt++;
				
			}
			solution.put(revs.get(cnt1).reviewer_id, list);
			
			
		}
//		boolean test = false;
//		for(short papcnt : paperReviewers.keySet())
//		{
//			System.out.println("Paper "+ papcnt + " Number of Reviewers " + paperReviewers.get(papcnt));
//		  	if(paperReviewers.get(papcnt)==0)
//		  	{
//		  		System.out.println("This paper is not assigned. " + papcnt);
//		  		test = true;
//		  	}
//		  	if(paperReviewers.get(papcnt)<Constants.MIN_NUM_OF_REVIEWERS_PER_PAPER)
//		  	{
//		  		System.out.println("This paper is not assigned to enought reviewers. " + papcnt);
//		  		test = true;
//		  	}
//		}
//		if(!test)
//		{
//			System.out.println("All papers are assigned.");
//		}
		
		return solution;
	
	}

	/**
	 * This method finds the quality of each assignment for LP problem
	 * formulation.
	 * 
	 * @param the_reviewer
	 * @param the_paper
	 * @return
	 */
	public short getSingleAssignmentQuality(Reviewer the_reviewer,
			Paper the_paper) {

		short quality = 0;
		for (short key : the_paper.getKeys()) {
			if (the_reviewer.getPreferneces().get(key) == ReviewerRank.YES) {
				quality += 1;
			} else if (the_reviewer.getPreferneces().get(key) == ReviewerRank.MAYBE) {
				quality += 0;
			} else if (the_reviewer.getPreferneces().get(key) == ReviewerRank.NO) {
				quality -= 1;
			}
		}
		return quality;

	}
}